package prome

import (
	"fmt"
	"gitee.com/zhucheer/orange/cfg"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
	"strings"
	"sync"
)

const (
	CounterVec   = "counter_vec"
	Counter      = "counter"
	GaugeVec     = "gauge_vec"
	Gauge        = "gauge"
	HistogramVec = "histogram_vec"
	Histogram    = "histogram"
	SummaryVec   = "summary_vec"
	Summary      = "summary"
)

// Standard default metrics
var reqCnt = &Metric{
	Name:        "requests_total",
	Description: "How many HTTP requests processed, partitioned by status code and HTTP method.",
	Type:        "counter_vec",
	Args:        []string{"method", "handler"}}

var reqDurSummary = &Metric{
	Name:        "request_duration_milliseconds",
	Description: "The HTTP request latencies in seconds.",
	Type:        "summary_vec",
	Args:        []string{"method", "handler"},
}

var srvErrCnt = &Metric{
	Name:        "api_error_total",
	Description: "How many service error",
	Type:        "counter_vec",
	Args:        []string{"method", "srv_name", "handler"}}

var standardMetrics = []*Metric{
	reqCnt,
	reqDurSummary,
	srvErrCnt,
}

// Metric is a definition for the name, description, type, ID, and
// prometheus.Collector type (i.e. CounterVec, Summary, etc) of each metric
type Metric struct {
	MetricCollector prometheus.Collector
	ID              string
	Name            string
	Description     string
	Type            string
	Args            []string
}

// Prometheus contains the metrics gathered by the instance and its path
type Prometheus struct {
	ReqCnt     *prometheus.CounterVec // 请求量指标
	SrvErrCnt  *prometheus.CounterVec // 服务异常指标
	DurSummary *prometheus.SummaryVec // 耗时指标

	MetricsList     []*Metric
	MetricsPath     string
	subsystem       string
	mutex           sync.Mutex
	customerMetrics map[string]prometheus.Collector
}

var promeHandler *Prometheus

// NewPrometheus generates a new set of metrics with a certain subsystem name
func NewPrometheus(subsystem string) *Prometheus {
	metricsList := make([]*Metric, 0)
	metricPath := cfg.GetString("prome.metric", "/metrics")
	for _, metric := range standardMetrics {
		metricsList = append(metricsList, metric)
	}

	subsystem = strings.ReplaceAll(subsystem, "-", "_")
	promeHandler = &Prometheus{
		MetricsList: metricsList,
		MetricsPath: metricPath,
		subsystem:   subsystem,
	}

	promeHandler.registerMetrics(subsystem)

	return promeHandler
}

// PrometheusHandler metrics指标httpHandler
func PrometheusHttpHandler() http.Handler {
	h := promhttp.Handler()
	return h
}

func PromeHandler() *Prometheus {
	if promeHandler == nil {
		panic("Prometheus has not instance.")
	}
	return promeHandler
}

func (p *Prometheus) PromeMetrics(name string) prometheus.Collector {
	if p.customerMetrics == nil {
		p.customerMetrics = make(map[string]prometheus.Collector)
	}
	if metrics, ok := p.customerMetrics[name]; ok {
		return metrics
	}

	panic("not register metrics:" + name)
}

func (p *Prometheus) RegisterCustomerMetrics(name string, Desc string, MetrixType string, Args []string) *Prometheus {
	if p.customerMetrics == nil {
		p.customerMetrics = make(map[string]prometheus.Collector)
	}

	if _, ok := p.customerMetrics[name]; ok == true {
		return p
	}
	p.mutex.Lock()
	defer p.mutex.Unlock()

	customer := &Metric{
		Name:        name,
		Description: Desc,
		Type:        MetrixType,
		Args:        Args,
	}

	metric := NewMetric(customer, p.subsystem)
	if err := prometheus.Register(metric); err != nil {
		fmt.Sprintf("%s could not be registered in Prometheus err %v", name, err)
		panic(err)
	}

	p.customerMetrics[name] = metric
	return p
}

// NewMetric associates prometheus.Collector based on Metric.Type
func NewMetric(m *Metric, subsystem string) prometheus.Collector {
	var metric prometheus.Collector
	switch m.Type {
	case CounterVec:
		metric = prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Subsystem: subsystem,
				Name:      m.Name,
				Help:      m.Description,
			},
			m.Args,
		)
	case Counter:
		metric = prometheus.NewCounter(
			prometheus.CounterOpts{
				Subsystem: subsystem,
				Name:      m.Name,
				Help:      m.Description,
			},
		)
	case GaugeVec:
		metric = prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Subsystem: subsystem,
				Name:      m.Name,
				Help:      m.Description,
			},
			m.Args,
		)
	case Gauge:
		metric = prometheus.NewGauge(
			prometheus.GaugeOpts{
				Subsystem: subsystem,
				Name:      m.Name,
				Help:      m.Description,
			},
		)
	case HistogramVec:
		metric = prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Subsystem: subsystem,
				Name:      m.Name,
				Help:      m.Description,
			},
			m.Args,
		)
	case Histogram:
		metric = prometheus.NewHistogram(
			prometheus.HistogramOpts{
				Subsystem: subsystem,
				Name:      m.Name,
				Help:      m.Description,
			},
		)
	case SummaryVec:
		metric = prometheus.NewSummaryVec(
			prometheus.SummaryOpts{
				Subsystem:  subsystem,
				Name:       m.Name,
				Help:       m.Description,
				Objectives: map[float64]float64{0.5: 0.05, 0.7: 0.03, 0.9: 0.01, 0.99: 0.001}, //分位数指标
			},
			m.Args,
		)
	case Summary:
		metric = prometheus.NewSummary(
			prometheus.SummaryOpts{
				Subsystem: subsystem,
				Name:      m.Name,
				Help:      m.Description,
			},
		)
	}
	return metric
}

func (p *Prometheus) registerMetrics(subsystem string) {

	for _, metricDef := range p.MetricsList {
		metric := NewMetric(metricDef, subsystem)
		if err := prometheus.Register(metric); err != nil {
			fmt.Sprintf("%s could not be registered in Prometheus err %v", metricDef.Name, err)
		}
		switch metricDef {
		case srvErrCnt:
			p.SrvErrCnt = metric.(*prometheus.CounterVec)
		case reqDurSummary:
			p.DurSummary = metric.(*prometheus.SummaryVec)
		case reqCnt:
			p.ReqCnt = metric.(*prometheus.CounterVec)
		}
	}

}
